<?php if (!defined('CORE_DIR')) die('Wrong entry point!');

/* 
 * Copyright 2010 Anton Muraviev a.k.a kuromaisu
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *     http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 */

// Set default timezone
date_default_timezone_set(Aero::option('time.timezone','UCT'));



function ae($package, $string, $arguments = array())
//
// Parameters:
// * `string` -- package name or `null`
// * `string` -- string name or actual string
// * `misc` -- (optional) string arguments
//
{
	return AeroLocale::localise($package, $string, $arguments);
}



abstract class AeroLocale
// Configuration namespace:
// * i18n
// 
// Configuration options:
// * locale -- 'en-us', 'en-gb', etc; default is 'en-gb';
// * path -- 'locales' by default.
{
	protected $arguments = array();
	static $loaded = array();
	static $strings = array();
	
	public static function localise($package, $string, $arguments = array(), $locale = null)
	//
	// Parameters:
	// * `string` -- package name or `null`
	// * `string` -- string name or actual string
	// * `misc` -- (optional) string arguments
	//
	{
		if (is_null($locale))
		{
			$locale = Aero::option('i18n.locale','en-gb');
		}
		
		$localiser = Aero::load('libs/i18n/'.str_replace('-','_',$locale));
		
		if (is_null($package))
		{
			$text = $string;
		}
		// Check cache first
		else if (isset(self::$strings[$locale][$package]))
		{
			if (!isset(self::$strings[$locale][$package][$string]))
			{
				trigger_error('<var>$strings["'.$string.'"]</var> is not set in package "'. $package .'" for locale "'.$locale.'".', E_USER_NOTICE);
				return '';
			}
			
			$text = self::$strings[$locale][$package][$string];
		}
		// Load from file
		else
		{
			// TODO: Support locales inheritance. If en-gb does not have this string, use en-us instead.
			$path = Aero::option('i18n.path', 'locales');
			$path = aero_trim_path($path).'/'.$locale.'/'.$package;
			
			try {
				$path = Aero::basePathFor($path) . $path . EXT;
				
				// Include localisation path
				include $path;
				
				if (!isset($strings))
				{
					throw new AeroException('No <var>$strings</var> found in <kbd>'. $path . '</kbd>');
				}
				
				// Cache loaded strings
				self::$strings[$locale][$package] = $strings;
				
				if (!isset($strings[$string]))
				{
					throw new AeroException('<var>$strings["'.$string.'"]</var> is not set in <kbd>'. $path .'</kbd>');
				}
				
				$text = $strings[$string];
			} catch (AeroException $e) {
				$locale = $localiser->parentLocale();
				
				if (is_string($locale) && strlen($locale))
				{
					return self::localise($package, $string, $arguments, $locale);
				}
				
				throw $e;
			}
		}
		
		// If no arguments, simply return text. It is already localised. ;-)
		if (count($arguments) == 0)
		{
			return $text;
		}
		
		return $localiser->parse($text, $arguments);
	}
	
	private function parentLocale()
	{
		$path = Aero::pathOf(get_parent_class($this));
		
		if ($path === false)
		{
			return;
		}
		
		$name = str_replace('/','_',$path);
		
		// Search for for aa[_aa00] at the end of the path.
		preg_match('~([a-z]{2,3})(?:_([a-z0-9]{2,4}))?$~', $name, $backrefs);
		
		if (count($backrefs) === 2)
		{
			return $backrefs[1];
		}
		else if (count($backrefs) === 3)
		{
			return $backrefs[1].'-'.$backrefs[2];
		}
	}
	
	/*
	 * Method: parse
	 */
	public function parse($text, $arguments)
	{
		$this->arguments = is_array($arguments) ? $arguments : array($arguments);
		$string = preg_replace_callback('~
			{					# open bracket
				(\d+|\w+)		# 1: variable name (or index)
				(?:				# (optional) pipeline
					\:			# 
					(.*?)		# 2: pipeline
				)?				#
				(?<!\\\\)		# must not be preceeded by slash
			}					# close bracket
			~xi', array($this,'_parse'), $text);
		$this->arguments = array();
		
		return $string;
	}
	
	/*
	 * Method: _parse
	 */
	protected function _parse($matches)
	{
		$o = $matches[1];
		
		if (is_numeric($o))
		{
			$o = (int)$matches[1] - 1;
		}
		
		if (!isset($this->arguments[$o]))
		{
			return '';
		}
		else if (!isset($matches[2]))
		{
			return $this->arguments[$o];
		}
		
		$pipeline = Aero::load('libs/pipeline',str_replace(array('\{','\}'), array('{','}'), $matches[2]));
		$pipeline->addContext($this);
		
		return $pipeline->modify($this->arguments[$o]);
	}
	
	/*
	 * Method: spellout
	 * 
	 * Converts an integer into literal string, e.g "21" to "twenty one".
	 */
	public function spellout($number)
	{
		return $number;
	}
	
	/*
	 * Method: _spellout
	 * 
	 * Subclasses may call this helper method to spellout integers.
	 * 
	 * Returns:
	 * 	Array of strings.
	 */
	protected function _spellout($integer, $power = 0)
	{
		$segments = explode('_',number_format($integer, 0, '.', '_'));
		
		$result = array();
		
		foreach ($segments as $key => $value)
		{
			$current = (int)$value;
			$next = isset($segments[$key + 1]) ? (int)$segments[$key + 1] : 0;
			$power = count($segments) - $key - 1;
			
			$result = array_merge($result, $this->_spelloutThousands($current,$next,$power));
		}
		
		return $result;
	}

	/*
	 * Method: _spelloutThousands
	 * 
	 * Stringifies integers between 1 and 999.
	 * 
	 * Returns:
	 * 	Array of strings.
	 */
	protected function _spelloutThousands($current, $next, $power)
	{
		$a100 = floor($current / 100);
		$u100 = $current % 100; 
		
		$return = array();
		$hundreds = $this->_spelloutHundreds($a100,$power);
		$decimals = $this->_spelloutDecimals($u100,$power) . $this->_spelloutMagnitude($current,$power);
		
		if (!empty($hundreds))
		{
			$return[] = $hundreds;
		}
		
		if (!empty($decimals))
		{
			$return[] = $decimals;
		}
		
		return $return;
	}
	
	/*
	 * Method: _spelloutHundreds
	 * 
	 * This method is called for "hundreds", when calling _spelloutThousands().
	 * 
	 * Parameters:
	 * 	int	-	0..9
	 * 	int	-	1/3 of current power
	 * 
	 * Returns:
	 * 	string
	 */
	protected function _spelloutHundreds($hundred,$power) { return ''; }

	/*
	 * Method: _spelloutDecimals
	 * 
	 * This method is called for "decimals", when calling _spelloutThousands().
	 * 
	 * Parameters:
	 * 	int	-	0..99
	 * 	int	-	1/3 of current power
	 * 
	 * Returns:
	 * 	string
	 */
	protected function _spelloutDecimals($decimal,$power) {  return ''; }
	
	/*
	 * Method: _spelloutMagnitude
	 * 
	 * Should append 'thousand', 'million', etc., when calling _spelloutThousands().
	 * 
	 * Parameters
	 * 	int	-	number
	 *  int	-	0..infinity; 1/3 of real power.
	 * 
	 * Returns:
	 * 	string
	 */
	protected function _spelloutMagnitude($integer,$power) {  return ''; }

	/*
	 * Method: inflect
	 * 
	 * Returns correct plural (or singular) form.
	 */
	public function inflect($number)
	{
		return func_get_arg(((int)$number === 1) ? 1 : 2);
	}

	/*
	 * Method: date
	 */
	public function date($datetime, $format)
	{
		return date($format,strtotime($datetime));
	}
	
	/*
	 * Method: month
	 */
	public function month($datetime)
	{
		return $this->date($datetime,'M');
	}
	
	/*
	 * Method: week
	 */
	public function weekday($datetime)
	{
		return $this->date($datetime,'l');
	}
}
